﻿using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Web.Hosting;
using System.Web.Mvc;
using SlamCms.Common;

namespace SlamCms.Web.Mvc
{
	public class ExtendedFileStreamResult : FileStreamResult
	{
		public ExtendedFileStreamResult(Stream fileStream, string contentType)
			: base(fileStream, contentType)
		{
			this.Etag = null;
		}

		public string Etag { get; set; }
		public DateTime? LastModified { get; set; }
		public bool NoContentTypeSniffing { get; set; }
		public bool NoETagGeneration { get; set; }
		public bool IsInlineDownload { get; set; }

		public ExtendedFileStreamResult SetEtag(string etag)
		{
			this.Etag = etag;
			return this;
		}

		public ExtendedFileStreamResult SetLastModified(DateTime lastModified)
		{
			this.LastModified = lastModified;
			return this;
		}

		public ExtendedFileStreamResult SetNoContentTypeSniffing()
		{
			this.NoContentTypeSniffing = true;
			return this;
		}

		public ExtendedFileStreamResult SetNoETagGeneration()
		{
			this.NoETagGeneration = true;
			return this;
		}

		public ExtendedFileStreamResult SetIsInlineDownload(bool isInlineDownload)
		{
			this.IsInlineDownload = isInlineDownload;
			return this;
		}

		public ExtendedFileStreamResult SetFileDownloadName(string fileDownloadName)
		{
			this.FileDownloadName = fileDownloadName;
			return this;
		}

		public override void ExecuteResult(ControllerContext context)
		{
			var response = context.HttpContext.Response;

			this.SetResponseHeaders(response);

			if (!context.HttpContext.IsSourceModified(this.Etag, this.LastModified))
			{
				response.SetNotModified();
				return;
			}

			base.ExecuteResult(context);

			this.SetResponseHeaders(response);
		}

		protected void SetResponseHeaders(HttpResponseBase response)
		{
			if (!string.IsNullOrWhiteSpace(this.Etag) && (!HttpRuntime.UsingIntegratedPipeline || response.Headers["ETag"] == null))
				response.AddHeader("ETag", this.Etag);

			if (this.LastModified.HasValue && (!HttpRuntime.UsingIntegratedPipeline || response.Headers["Last-Modified"] == null))
				response.AddHeader("Last-Modified", this.LastModified.Value.ToString("r"));

			if (this.NoContentTypeSniffing && (!HttpRuntime.UsingIntegratedPipeline || response.Headers["X-Content-Type-Options"] == null))
				response.AddHeader("X-Content-Type-Options", "nosniff");

			if (this.IsInlineDownload)
				response.Headers["Content-Disposition"] = "inline; filename={0}".F(this.FileDownloadName);
		}

		protected override void WriteFile(HttpResponseBase response)
		{
			Stream outputStream = response.OutputStream;
			var etagStream = new MemoryStream();

			using (this.FileStream)
			{
				byte[] buffer = new byte[0x1000];
				while (true)
				{
					int count = this.FileStream.Read(buffer, 0, 0x1000);
					if (count == 0)
						break;

					outputStream.Write(buffer, 0, count);

					// stream for etag generation
					if (this.Etag == null && !this.NoETagGeneration)
						etagStream.Write(buffer, 0, count);
				}
			}

			if (this.Etag == null && !this.NoETagGeneration)
			{
				etagStream.Seek(0, SeekOrigin.Begin);
				this.Etag = GenerateETag(etagStream);
			}
		}

		protected string GenerateETag(Stream stream)
		{
			var checksum = MD5.Create().ComputeHash(stream);
			return Convert.ToBase64String(checksum, 0, checksum.Length);
		}

		protected string GenerateETag(string value)
		{
			var bytes = Encoding.UTF8.GetBytes(value);
			var checksum = MD5.Create().ComputeHash(bytes);
			return Convert.ToBase64String(checksum, 0, checksum.Length);
		}
	}
}